﻿/***********************************************************************************************
COPYRIGHT 2008 Vijeth D

This file is part of Handwritten Character Recognition - NeuronDotNet Sample.
(Project Website : http://neurondotnet.freehostia.com)

NeuronDotNet is a free software. You can redistribute it and/or modify it under the terms of
the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.

NeuronDotNet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with NeuronDotNet.
If not, see <http://www.gnu.org/licenses/>.

***********************************************************************************************/

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.Serialization;

namespace NeuronDotNet.Controls
{
  [Serializable]
  public class Letter : ISerializable
  {
    #region public fields
    public int left = int.MaxValue;
    public int right;
    public int top = int.MaxValue;
    public int bottom;
    #endregion

    #region private fields
    private readonly List<Stroke> strokes = new List<Stroke>();
    private bool isUpdate = true;
    private const int LeftOffset = 1;
    private const int TopOffset = 1;
    private const int WidthOffset = 1;
    private const int HeightOffset = 2;
    #endregion

    #region properties
    public int StrokesCount
    {
      get { return strokes.Count; }
    }
    public Rectangle Bounds
    {
      get { return new Rectangle(left, top, right - left, bottom - top); }
    }
    #endregion

    #region enumerator
    public IEnumerable<Stroke> Strokes
    {
      get
      {
        Update();
        for (int i = 0; i < strokes.Count; i++)
        {
          yield return strokes[i];
        }
      }
    }
    #endregion


    #region constructors
    public Letter()
    {
    }

    public Letter(IEnumerable<Stroke> strokes)
      : this()
    {
      Helper.ValidateNotNull(strokes, "strokes");

      foreach (Stroke stroke in strokes)
      {
        AddStroke(new Stroke(stroke));
      }
      isUpdate = false;
      Update();
    }

    public Letter(Letter letter)
    {
      Helper.ValidateNotNull(letter, "letter");

      for (int i = 0; i < letter.strokes.Count; i++)
      {
        strokes.Add(new Stroke(letter.strokes[i]));
      }
      top = letter.top;
      bottom = letter.bottom;
      left = letter.left;
      right = letter.right;
      Update();
    }
    #endregion

    #region public methods
    public Letter(SerializationInfo info, StreamingContext context)
    {
      Helper.ValidateNotNull(info, "info");

      strokes = (List<Stroke>)info.GetValue("strokes", typeof(List<Stroke>));
      left = info.GetInt32("left");
      right = info.GetInt32("right");
      top = info.GetInt32("top");
      bottom = info.GetInt32("bottom");
      isUpdate = false;
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
      Helper.ValidateNotNull(info, "info");
      info.AddValue("strokes", strokes, typeof(List<Stroke>));
      info.AddValue("left", left);
      info.AddValue("right", right);
      info.AddValue("top", top);
      info.AddValue("bottom", bottom);
    }

    public void AddStroke(Stroke stroke)
    {
      Helper.ValidateNotNull(stroke, "stroke");
      strokes.Add(stroke);

      left = Math.Min(left, stroke.Bounds.Left);
      right = Math.Max(right, stroke.Bounds.Right);
      top = Math.Min(top, stroke.Bounds.Top);
      bottom = Math.Max(bottom, stroke.Bounds.Bottom);
      isUpdate = false;
    }

    #region our method
    /// <summary>
    /// Creates a Bitmap with the size of the letter boundaries.
    /// </summary>
    public Bitmap MakeBitmap()
    {
      int letterWidth = right - left;
      int letterHeight = bottom - top;

      double[] bitArray = GetEquivalentVector(letterHeight, letterWidth);
      //create a bitmap of the whole drawing     
      var map = new Bitmap(letterWidth, letterHeight);

      int k = 0;
      for (int i = 0; i < letterHeight; i++)
      {
        for (int j = 0; j < letterWidth; j++)
        {
          //Transfer the value from double[0,1] to integer in the range [0,255]
          var darkness = (int)((1 - bitArray[k++]) * 255);
          //generate Color in ARGB.
          Color colorInArgb = Color.FromArgb(darkness, darkness, darkness);
          //set this Color to (j,i) pixel.
          map.SetPixel(j, i, colorInArgb);
        }
      }
      return map;
    }

    /// <summary>
    /// Get border of letter return Letter(Class).
    /// </summary>
    public Letter MakeCliped(Rectangle bounds)
    {
      var resultLetter = new Letter(this);
      Rectangle tempBounds = bounds;
      tempBounds.Intersect(Bounds);
        
      //Regulate bounds of letter.
      int leftBoundWithOffset = tempBounds.Left - LeftOffset;
      int topBoundWithOffset = tempBounds.Top - TopOffset;
      int widthBoundWithOffset = tempBounds.Width + WidthOffset;
      int heightBoundWithOffset = tempBounds.Height + HeightOffset;

      tempBounds = new Rectangle(leftBoundWithOffset,
        topBoundWithOffset,
        widthBoundWithOffset,
        heightBoundWithOffset);
      resultLetter.Clip(tempBounds);
      return resultLetter;
    }

    /// <summary>
    /// Clip letter.
    /// </summary>
    public void Clip(Rectangle bounds)
    {
      left = int.MaxValue;
      right = 0;
      top = int.MaxValue;
      bottom = 0;

      for (int i = 0; i < strokes.Count; i++)
      {
        strokes[i].Clip(bounds);
        if (strokes[i].PointsCount == 0)
        {
          strokes.RemoveAt(i);
          continue;
        }
        Rectangle currentBounds = strokes[i].Bounds;
        left = Math.Min(left, currentBounds.Left);
        right = Math.Max(right, currentBounds.Right);
        top = Math.Min(top, currentBounds.Top);
        bottom = Math.Max(bottom, currentBounds.Bottom);
      }
      isUpdate = true;
    }
    #endregion
    /// <summary>
    /// Get Equivalent Vector. 
    /// </summary>
    public double[] GetEquivalentVector(int rowCount, int columnCount)
    {
      return GetEquivalentVector(rowCount, columnCount, useRandomOffset: false);
    }


    public double[] GetEquivalentVector(int rowCount, int columnCount, bool useRandomOffset)
    {
      int left = this.left;
      int right = this.right;
      int top = this.top;
      int bottom = this.bottom;
      int width = right - left;
      int height = bottom - top;

      if (useRandomOffset)
      {
        left = Math.Max(0, left - (int)(width * Helper.GetRandom() * 0.6));
        right = right + (int)(width * Helper.GetRandom() * 0.3);
        top = Math.Max(0, top - (int)(height * Helper.GetRandom() * 0.6));
        bottom = bottom + (int)(width * Helper.GetRandom() * 0.3);
        width = right - left;
        height = bottom - top;
      }

      int scale = Math.Max(width, height) + 1;

      double xUnit = ((double)scale) / (columnCount);
      double yUnit = ((double)scale) / (rowCount);
      double[] data = new double[columnCount * rowCount];

      if (scale < 0)
      {
        return data;
      }

      int[,] map = new int[scale, scale];
      for (int k = 0; k < strokes.Count; k++)
      {
        foreach (Line line in strokes[k].Lines)
        {
          // proceed only inner lines
          if (!line.IsInside(new Rectangle(left, top, width, height)))
          {
            continue;
          }

          if (line == null) continue;
          int xStart = line.Start.X;
          int yStart = line.Start.Y;

          int xk = line.Start.X - left;
          int yk = line.Start.Y - top;

          int dx = Math.Abs(line.End.X - xStart);
          int dy = Math.Abs(line.End.Y - yStart);
          int twoDx = 2 * dx;
          int twoDy = 2 * dy;

          int xInc = Math.Sign(line.End.X - xStart);
          int yInc = Math.Sign(line.End.Y - yStart);

          if (twoDx >= twoDy)
          {
            int pk = twoDy - dx;
            for (int i = 0; i <= dx; i++, xk += xInc, pk += twoDy)
            {
              map[xk, yk] = 1;
              if (pk >= 0)
              {
                pk -= twoDx;
                yk += yInc;
              }
            }
          }
          else
          {
            int pk = twoDx - dy;
            for (int i = 0; i <= dy; i++, pk += twoDx, yk += yInc)
            {
              map[xk, yk] = 1;
              if (pk >= 0)
              {
                pk -= twoDy;
                xk += xInc;
              }
            }
          }
        }
      }

      double max = 0d;
      for (int i = 0; i < rowCount; i++)
      {
        for (int j = 0; j < columnCount; j++)
        {
          int count = 0;
          int total = 0;
          for (int y = (int)(i * yUnit); y < (i + 1) * yUnit && y < scale; y++)
          {
            for (int x = (int)(j * xUnit); x < (j + 1) * xUnit && x < scale; x++)
            {
              count += map[x, y];
              total++;
            }
          }
          data[i * columnCount + j] = ((double)count) / total;
          if (max < data[i * columnCount + j])
          {
            max = data[i * columnCount + j];
          }
        }
      }
      if (max > 0)
      {
        for (int i = 0; i < data.Length; i++)
        {
          data[i] /= max;
        }
      }
      return data;
    }
    #endregion

    #region virtual method
    protected virtual void Update()
    {
      if (isUpdate) { return; }

      var splitStrokes = new List<Stroke>();
      for (int i = 0; i < strokes.Count; i++)
      {
        splitStrokes.AddRange(strokes[i].Split());
      }
      strokes.Clear();
      strokes.AddRange(splitStrokes);
      isUpdate = true;
    }
    #endregion
  }
}